package com.special.threademo.common.excel;

import com.nimbusds.oauth2.sdk.util.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.*;
import java.util.function.Consumer;

/**
 * @author LiuYC
 * @version 1.0.0
 * @create 2020-12-21 10:26
 */

public class ExcelImportUtil<T> implements BaseImportUtil<T> {
    protected static final String OFFICE_EXCEL_2003_POSTFIX = "xls";
    protected static final String OFFICE_EXCEL_2010_POSTFIX = "xlsx";
    private List<T> tList = new ArrayList<>();
    private TreeMap<String, String> relations = new TreeMap<>();
    private TreeMap<Integer, String> headerSort;
    private MultipartFile multipartFile;
    private Class<T> tClass;

    private ExcelImportUtil() {

    }

    public static <T> ExcelImportUtil<T> builder(Class<T> tClass) {
        ExcelImportUtil<T> tExcelImportUtil = new ExcelImportUtil<>();
        tExcelImportUtil.tClass = tClass;
        return tExcelImportUtil;
    }

    public ExcelImportUtil<T> withRelation(String key, String value) {
        this.relations.put(key, value);
        return this;
    }

    public ExcelImportUtil<T> withFile(MultipartFile multipartFile) {
        this.multipartFile = multipartFile;
        return this;
    }

    @Override
    public void importExcel(Consumer<List<T>> a) {
        try {
            String fileName = multipartFile.getOriginalFilename();
            if (StringUtils.isNotBlank(fileName)) {
                int index = fileName.lastIndexOf("\\");
                String newStr = index > -1 ? fileName.substring(index + 1) : fileName;
                fileName = !"".equals(newStr) ? newStr : fileName;
                InputStream inputStream = multipartFile.getInputStream();
                process(inputStream, fileName);
                a.accept(tList);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void process(InputStream inputStream, String fileName) throws IOException {
        if (fileName.contains(OFFICE_EXCEL_2010_POSTFIX)) {
            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(inputStream);
            processReal(xssfWorkbook);
        } else if (fileName.contains(OFFICE_EXCEL_2003_POSTFIX)) {
            HSSFWorkbook hssfWorkbook = new HSSFWorkbook(inputStream);
            processReal(hssfWorkbook);
        }
    }

    public static boolean isRowEmpty(Row row) {
        for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) {
            Cell cell = row.getCell(i);
            if (cell != null && cell.getCellType() != CellType.BLANK) {
                return false;
            }
        }
        return true;
    }

    private void initHeaderSort(Row row) {

        if (!isRowEmpty(row)) {
            this.headerSort = new TreeMap<>();
            for (int currentNum = row.getFirstCellNum(); currentNum <= (int) row.getLastCellNum(); currentNum++) {
                Optional.ofNullable(row.getCell(currentNum)).ifPresent(xssfCell -> {
                    if (xssfCell.getCellType() != CellType.BLANK && xssfCell != null) {
                        this.headerSort.put(xssfCell.getColumnIndex(), getCellValue(xssfCell));
                    }
                });
            }
        }
    }


    private String getCellValue(Cell cell) {
        if (cell.getCellType() == CellType.NUMERIC) {
            if (DateUtil.isCellDateFormatted(cell)) {
                Date date = cell.getDateCellValue();
                return String.valueOf(date.getTime());
            } else if (cell.getCellStyle().getDataFormat() == 58) {
                Date date = DateUtil.getJavaDate(cell.getNumericCellValue());
                return String.valueOf(date.getTime());
            }
            NumberFormat instance = NumberFormat.getInstance();
            String format = instance.format(cell.getNumericCellValue());
            return format.replaceAll(",", "").toString();
        } else if (cell.getCellType() == CellType.BLANK) {
            throw new IndexOutOfBoundsException();
//            throw new CuckooException("E99008", "ECXEL数据有空格");
        }
        return String.valueOf(cell.getStringCellValue());
    }


    /**
     * 去空格
     *
     * @param str
     * @return
     */
    private String StringTrim(String str) {
        return str.replaceAll("[\\s\\u00A0]+", "").trim();
    }

    private void processReal(Workbook workbook) {
        int sheetsNum = workbook.getNumberOfSheets();
        for (int currentNum = 0; currentNum < sheetsNum; currentNum++) {
            Optional.ofNullable(workbook.getSheetAt(currentNum)).ifPresent(sheet -> {
                initHeaderSort(sheet.getRow(sheet.getFirstRowNum()));
                for (int rowNum = sheet.getFirstRowNum() + 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
                    Optional.ofNullable(sheet.getRow(rowNum)).ifPresent(row -> {
                        if (!isRowEmpty(row)) {
                            T t = newInstance();
                            if (t != null) {
                                Map<String, String> valueMap = new HashMap<>();
                                for (int currentCellNum = (int) row.getFirstCellNum(); currentCellNum <= (int) row.getLastCellNum(); currentCellNum++) {
                                    Optional.ofNullable(row.getCell(currentCellNum)).ifPresent(xssfCell -> {
                                        if (xssfCell.getCellType() != CellType.BLANK) {
                                            String headerName = this.headerSort.get(xssfCell.getColumnIndex());
                                            String headerBeanName = relations.get(headerName);
                                            String cellValue = getCellValue(xssfCell);
                                            if (cellValue.replace(String.valueOf((char) 160), " ") != null) {
                                                valueMap.put(headerBeanName, cellValue);
                                            }
                                        }
                                    });
                                }
                                setValue(t, valueMap);
                                tList.add(t);

                            }
                        }
                    });
                }
            });
        }
    }

    private void setValue(T t, Map<String, String> valueMap) {
        Class clazz = t.getClass();
        Arrays.stream(clazz.getDeclaredFields()).forEach(field -> {
            field.setAccessible(true);
            String name = field.getName();
            String value = valueMap.get(name);
            String fieldClassName = field.getType().getSimpleName();
            try {
                if ("String".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? value : "");
                } else if ("boolean".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, true);
                } else if ("int".equalsIgnoreCase(fieldClassName) || "Integer".equals(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? Integer.parseInt(value) : null);
                } else if ("double".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? String.valueOf(Double.parseDouble(value)) : null);
                } else if ("long".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? Long.parseLong(value) : null);
                } else if ("BigDecimal".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? BigDecimal.valueOf(Long.parseLong(value)) : null);
                } else if ("float".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? Float.parseFloat(value) : null);
                } else if ("date".equalsIgnoreCase(fieldClassName)) {
                    field.set(t, StringUtils.isNotBlank(value) ? new Date(Long.parseLong(value)) : null);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }

    private T newInstance() {
        try {
            return this.tClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

}

